#ifndef TMATRIX_HPP
#define TMATRIX_HPP

#include "tvector.hpp"

#pragma region matrix base element template

template <typename T, ui8 col, ui8 row>
struct _tmatrix_base_accessor {};

template <typename T>
struct _tmatrix_base_accessor<T, 1, 1> {
  union {
    struct {
      T _m11;
    };
    T _;
  };
  _tmatrix_base_accessor() { _ = 0; }
  _tmatrix_base_accessor( T m11 ) { _ = m11; }
};

template <typename T>
struct _tmatrix_base_accessor<T, 1, 2> {
  union {
    struct {
      T _m11;
      T _m21;
    };
    T _[2];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 2 ) }
  _tmatrix_base_accessor( T m11, T m21 ) {
    _[0] = m11;
    _[1] = m21;
  }
};

template <typename T>
struct _tmatrix_base_accessor<T, 1, 3> {
  union {
    struct {
      T _m11;
      T _m21;
      T _m31;
    };
    T _[3];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 3 ) }
  _tmatrix_base_accessor( T m11, T m21, T m31 ) {
    _[0] = m11;
    _[1] = m21;
    _[2] = m31;
  }
};

template <typename T>
struct _tmatrix_base_accessor<T, 1, 4> {
  union {
    struct {
      T _m11;
      T _m21;
      T _m31;
      T _m41;
    };
    T _[4];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 4 ); }
  _tmatrix_base_accessor( T m11, T m21, T m31, T m41 ) {
    _[0] = m11;
    _[1] = m21;
    _[2] = m31;
    _[3] = m41;
  }
};

template <typename T>
struct _tmatrix_base_accessor<T, 2, 2> {
  union {
    struct {
      T _m11, _m21;
      T _m12, _m22;
    };
    T _[4];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 4 ) }
  _tmatrix_base_accessor( T m11, T m21, T m12, T m22 ) {
    _[0] = m11, _[2] = m12;
    _[1] = m21, _[3] = m22;
  }
};

template <typename T>
struct _tmatrix_base_accessor<T, 2, 3> {
  union {
    struct {
      T _m11, _m21, _m31;
      T _m12, _m22, _m32;
    };
    T _[6];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 6 ) }
  _tmatrix_base_accessor( T m11, T m21, T m31, T m12, T m22, T m32 ) {
    _[0] = m11, _[3] = m12;
    _[1] = m21, _[4] = m22;
    _[2] = m31, _[5] = m32;
  }
};

template <typename T>
struct _tmatrix_base_accessor<T, 2, 4> {
  union {
    struct {
      T _m11, _m21, _m31, _m41;
      T _m12, _m22, _m32, _m42;
    };
    T _[8];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 8 ) }
  _tmatrix_base_accessor( T m11, T m21, T m31, T m41, T m12, T m22, T m32,
                          T m42 ) {
    _[0] = m11, _[4] = m12;
    _[1] = m21, _[5] = m22;
    _[2] = m31, _[6] = m32;
    _[3] = m41, _[7] = m42;
  }
};

template <typename T>
struct _tmatrix_base_accessor<T, 3, 3> {
  union {
    struct {
      T _m11, _m21, _m31;
      T _m12, _m22, _m32;
      T _m13, _m23, _m33;
    };
    T _[9];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 9 ) }
  _tmatrix_base_accessor( T _m11, T _m21, T _m31, T _m12, T _m22, T _m32,
                          T _m13, T _m23, T _m33 ) {
    _[0] = _m11, _[3] = _m12, _[6] = _m13;
    _[1] = _m21, _[4] = _m22, _[7] = _m23;
    _[2] = _m31, _[5] = _m32, _[8] = _m33;
  }
};

template <typename T>
struct _tmatrix_base_accessor<T, 3, 4> {
  union {
    struct {
      // ! Bug adapt with the col, row order
      T _m11, _m21, _m31, _m41;
      T _m12, _m22, _m32, _m42;
      T _m13, _m23, _m33, _m43;
    };
    T _[12];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 12 ) }
  _tmatrix_base_accessor( T _m11, T _m21, T _m31, T _m41, T _m12, T _m22,
                          T _m32, T _m42, T _m13, T _m23, T _m33, T _m43 ) {
    _[0] = _m11, _[4] = _m12, _[8] = _m13;
    _[1] = _m21, _[5] = _m22, _[9] = _m23;
    _[2] = _m31, _[6] = _m32, _[10] = _m33;
    _[3] = _m41, _[7] = _m42, _[11] = _m43;
  }
};

template <typename T>
struct _tmatrix_base_accessor<T, 4, 4> {
  union {
    struct {
      T _m11, _m21, _m31, _m41;
      T _m12, _m22, _m32, _m42;
      T _m13, _m23, _m33, _m43;
      T _m14, _m24, _m34, _m44;
    };
    T _[16];
  };
  _tmatrix_base_accessor() { LOOP_TMAT( _[i] = 0;, 16 ) }
  _tmatrix_base_accessor( T _m11, T _m21, T _m31, T _m41, T _m12, T _m22,
                          T _m32, T _m42, T _m13, T _m23, T _m33, T _m43,
                          T _m14, T _m24, T _m34, T _m44 ) {
    _[0] = _m11, _[4] = _m12, _[8] = _m13, _[12] = _m14;
    _[1] = _m21, _[5] = _m22, _[9] = _m23, _[13] = _m24;
    _[2] = _m31, _[6] = _m32, _[10] = _m33, _[14] = _m34;
    _[3] = _m41, _[7] = _m42, _[11] = _m43, _[15] = _m44;
  }
};

#pragma endregion

/**
 *
 * @brief column order matrix class
 * @note
 * @tparam T the data type
 * @tparam col the column number
 * @tparam row the row number
 * @retval None
 */
// ! Bug in here the col and row
template <typename T, ui8 col, ui8 row>
struct tmatrix : _tmatrix_base_accessor<T, col, row> {

  using ColType = decltype( col );
  using RowType = decltype( row );
  using IndexType = decltype( col );

  tmatrix() : _tmatrix_base_accessor() {}

  template <typename... Ts>
  tmatrix( T a, Ts... args ) {
    constexpr bool value = ( std::is_convertible_v<decltype( args ), T>, ... );
    static_assert( value, "The arg type can not convert to type T" );
    if constexpr ( value ) {
      IndexType i = 0;
      deduce( i++, a );
      ( deduce( i++, static_cast<T>( args ) ), ... );
    }
  }

  tmatrix( T s ) {
    // LOOP_TMAT( _[i * ( row + 1 )] = s;, col )
    for ( IndexType i = 0; i < col; i++ ) {
      _[i * ( row + 1 )] = s;
    }
  }

private:
  void deduce( IndexType i, T e ) {
    if ( i >= col * row ) return;
    _[i++] = e;
  }

public:
  // if the @p r and @p c is not satisfied the condition, just reture the first
  // element
  inline T operator()( RowType r, ColType c ) const {
    if ( ( c > col ) || ( r > row ) ) return _[0];
    return _[row * c + r];
  }

  inline T &operator()( RowType r, ColType c ) {
    if ( ( c > col ) || ( r > row ) ) return _[0];
    return _[row * c + r];
  }

  // Get column vector base index from 0 to col or row
  inline tvec<T, row> colvec( IndexType index ) const {
    tvec<T, row> r;
    LOOP_TMAT( r[i] = _[( index % col ) * row + i];, row )
    return r;
  }

  inline void setCol( IndexType index, const tvec<T, row> &v ) {
    LOOP_TMAT( _[( index % col ) * row + i] = v[i];, row )
  }

  inline tvec<T, col> rowvec( IndexType index ) const {
    tvec<T, col> r;
    LOOP_TMAT( r[i] = _[( index % col ) + i * col];, col )
    return r;
  }

  inline void setRow( IndexType index, const tvec<T, col> &v ) {
    LOOP_TMAT( _[( index % col ) + i * col] = v[i];, col )
  }
};

template <typename T, ui8 row, ui8 col, ui8 ncol>
inline tmatrix<T, ncol, row> operator*( const tmatrix<T, col, row> &lm,
                                        const tmatrix<T, ncol, col> &rm ) {
  tmatrix<T, ncol, row> result;
  // col, row order
  for ( ui8 c = 0; c < ncol; c++ ) {
    for ( ui8 k = 0; k < row; k++ ) {
      for ( ui8 r = 0; r < ncol; r++ ) {
        result( k, c ) += lm( k, r ) * rm( r, c );
      }
    }
  }
  // row, col order
  // for ( ui8 c = 0; c < ncol; c++ ) {
  //   for ( ui8 k = 0; k < row; k++ ) {
  //     for ( ui8 r = 0; r < row; r++ ) {
  //       result( k, c ) += lm( k, r ) * rm( r, c );
  //     }
  //   }
  // }
  return result;
}

#pragma region Alias name for convient use

#pragma region Integer Type

using int1x1 = tmatrix<i32, 1, 1>;
using int2x1 = tmatrix<i32, 1, 2>;
using int3x1 = tmatrix<i32, 1, 3>;
using int4x1 = tmatrix<i32, 1, 4>;
using int3x2 = tmatrix<i32, 2, 3>;
using int4x2 = tmatrix<i32, 2, 4>;
using int3x3 = tmatrix<i32, 3, 3>;
using int4x3 = tmatrix<i32, 3, 4>;
using int4x4 = tmatrix<i32, 4, 4>;

using uint1x1 = tmatrix<ui32, 1, 1>;
using uint2x1 = tmatrix<ui32, 1, 2>;
using uint3x1 = tmatrix<ui32, 1, 3>;
using uint4x1 = tmatrix<ui32, 1, 4>;
using uint3x2 = tmatrix<ui32, 2, 3>;
using uint4x2 = tmatrix<ui32, 2, 4>;
using uint3x3 = tmatrix<ui32, 3, 3>;
using uint4x3 = tmatrix<ui32, 3, 4>;
using uint4x4 = tmatrix<ui32, 4, 4>;

using ubyte1x1 = tmatrix<ui8, 1, 1>;
using ubyte2x1 = tmatrix<ui8, 1, 2>;
using ubyte3x1 = tmatrix<ui8, 1, 3>;
using ubyte4x1 = tmatrix<ui8, 1, 4>;
using ubyte3x2 = tmatrix<ui8, 2, 3>;
using ubyte4x2 = tmatrix<ui8, 2, 4>;
using ubyte3x3 = tmatrix<ui8, 3, 3>;
using ubyte4x3 = tmatrix<ui8, 3, 4>;
using ubyte4x4 = tmatrix<ui8, 4, 4>;

using byte1x1 = tmatrix<i8, 1, 1>;
using byte2x1 = tmatrix<i8, 1, 2>;
using byte3x1 = tmatrix<i8, 1, 3>;
using byte4x1 = tmatrix<i8, 1, 4>;
using byte3x2 = tmatrix<i8, 2, 3>;
using byte4x2 = tmatrix<i8, 2, 4>;
using byte3x3 = tmatrix<i8, 3, 3>;
using byte4x3 = tmatrix<i8, 3, 4>;
using byte4x4 = tmatrix<i8, 4, 4>;

using short1x1 = tmatrix<i16, 1, 1>;
using short2x1 = tmatrix<i16, 1, 2>;
using short3x1 = tmatrix<i16, 1, 3>;
using short4x1 = tmatrix<i16, 1, 4>;
using short3x2 = tmatrix<i16, 2, 3>;
using short4x2 = tmatrix<i16, 2, 4>;
using short3x3 = tmatrix<i16, 3, 3>;
using short4x3 = tmatrix<i16, 3, 4>;
using short4x4 = tmatrix<i16, 4, 4>;

using ushort1x1 = tmatrix<ui16, 1, 1>;
using ushort2x1 = tmatrix<ui16, 1, 2>;
using ushort3x1 = tmatrix<ui16, 1, 3>;
using ushort4x1 = tmatrix<ui16, 1, 4>;
using ushort3x2 = tmatrix<ui16, 2, 3>;
using ushort4x2 = tmatrix<ui16, 2, 4>;
using ushort3x3 = tmatrix<ui16, 3, 3>;
using ushort4x3 = tmatrix<ui16, 3, 4>;
using ushort4x4 = tmatrix<ui16, 4, 4>;

using ull1x1 = tmatrix<ui32, 1, 1>;
using ull2x1 = tmatrix<ui32, 1, 2>;
using ull3x1 = tmatrix<ui32, 1, 3>;
using ull4x1 = tmatrix<ui32, 1, 4>;
using ull3x2 = tmatrix<ui32, 2, 3>;
using ull4x2 = tmatrix<ui32, 2, 4>;
using ull3x3 = tmatrix<ui32, 3, 3>;
using ull4x3 = tmatrix<ui32, 3, 4>;
using ull4x4 = tmatrix<ui32, 4, 4>;

using ll1x1 = tmatrix<i64, 1, 1>;
using ll2x1 = tmatrix<i64, 1, 2>;
using ll3x1 = tmatrix<i64, 1, 3>;
using ll4x1 = tmatrix<i64, 1, 4>;
using ll3x2 = tmatrix<i64, 2, 3>;
using ll4x2 = tmatrix<i64, 2, 4>;
using ll3x3 = tmatrix<i64, 3, 3>;
using ll4x3 = tmatrix<i64, 3, 4>;
using ll4x4 = tmatrix<i64, 4, 4>;

#pragma endregion

#pragma region Float Point Type

using float1x1 = tmatrix<f32, 1, 1>;
using float2x1 = tmatrix<f32, 1, 2>;
using float3x1 = tmatrix<f32, 1, 3>;
using float4x1 = tmatrix<f32, 1, 4>;
using float3x2 = tmatrix<f32, 2, 3>;
using float4x2 = tmatrix<f32, 2, 4>;
using float3x3 = tmatrix<f32, 3, 3>;
using float4x3 = tmatrix<f32, 3, 4>;
using float4x4 = tmatrix<f32, 4, 4>;

using double1x1 = tmatrix<f64, 1, 1>;
using double2x1 = tmatrix<f64, 1, 2>;
using double3x1 = tmatrix<f64, 1, 3>;
using double4x1 = tmatrix<f64, 1, 4>;
using double3x2 = tmatrix<f64, 2, 3>;
using double4x2 = tmatrix<f64, 2, 4>;
using double3x3 = tmatrix<f64, 3, 3>;
using double4x3 = tmatrix<f64, 3, 4>;
using double4x4 = tmatrix<f64, 4, 4>;

#pragma endregion

#pragma endregion

#include "print_mat.inl"

#endif